// exception.cc
//     Entry point into the Nachos kernel from user programs.
//     There are two kinds of things that can cause control to
//     transfer back to here from user code:
//
//     syscall -- The user code explicitly requests to call a procedure
//     in the Nachos kernel.  Right now, the only function we support is
//     "Halt".
//
//     exceptions -- The user code does something that the CPU can't handle.
//     For instance, accessing memory that doesn't exist, arithmetic errors,
//     etc.
//
//     Interrupts (which can also cause control to transfer from user
//     code into the Nachos kernel) are handled elsewhere.
//
// For now, this only handles the Halt() system call.
// Everything else core dumps.
//
// Copyright (c) 1992-1993 The Regents of the University of California.
// All rights reserved.  See copyright.h for copyright notice and limitation
// of liability and disclaimer of warranty provisions.

#include <stdlib.h>
#include <errno.h>

#include "copyright.h"
#include "system.h"
#include "syscall.h"
#include "thread.h"
#include "childthread.h"
#include "translate.h"

#include <fcntl.h>
#include <unistd.h>

static void machine_run(int parent_record_ptr) {
    currentThread->space->InitRegisters();
    currentThread->space->RestoreState();
    machine->Run();
}

static void run_forked(int parent_record_ptr) {
    currentThread->space->RestoreState();

    //Return 0
    machine->WriteRegister(2, 0);

    //Increment PC, so the child doesn't immediately call fork agian.
    machine->WriteRegister(PrevPCReg, machine->ReadRegister(PCReg));
    machine->WriteRegister(PCReg, machine->ReadRegister(NextPCReg));
    machine->WriteRegister(NextPCReg, machine->ReadRegister(PCReg)+4);

    machine->Run();
}

static void doNothingInterrupt(int blah) {
    return;
}

int sendKillSig(int signal, Thread *proc) {
    //immediatly set the thread to be finished
    if (signal == KILL_kill) {
        proc->notifyParent(-1);

        //tell the os that this thread should be destroyed by any means
        //necessary
        threadToBeDestroyed = proc;
        
    }
    //push interrupt on to stack and set this thread to be current thread
    else if (signal == KILL_int) {
        interrupt->Schedule(doNothingInterrupt, 0, 0, NetworkRecvInt);

    } else {
        //invalid signal sent
        return -1;
    }

    return 0;
}

//----------------------------------------------------------------------
// ExceptionHandler
//     Entry point into the Nachos kernel.  Called when a user program
//     is executing, and either does a syscall, or generates an addressing
//     or arithmetic exception.
//
//      For system calls, the following is the calling convention:
//
//      system call code -- r2
//          arg1 -- r4
//          arg2 -- r5
//          arg3 -- r6
//          arg4 -- r7
//
//     The result of the system call, if any, must be put back into r2.
//
// And don't forget to increment the pc before returning. (Or else you'll
// loop making the same system call forever!
//
//     "which" is the kind of exception.  The list of possible exceptions
//     are in machine.h.
//----------------------------------------------------------------------

void
ExceptionHandler(ExceptionType which) {
    int type = machine->ReadRegister(2);
    
    if (which == SyscallException) {
        DEBUG('t', "Syscall ID:%d\n", type);
        if (type == SC_Halt) {
            DEBUG('t', "Shutdown, initiated by user program.\n");
            stats->Print();
            interrupt->Halt();
        } else if (type == SC_Exit) {
            DEBUG('t', "\nSystem call Exit\n\n");
            //void Exit(status)
            int status = machine->ReadRegister(4);
            currentThread->notifyParent(status);
            DEBUG('t', "Thread %p exits with status %d\n",
                currentThread, status);

            //Finish flags the thread for deletion, and sleeps, causing a
            //context switch.
            currentThread->Finish();
        } else if (type == SC_Join) {
            DEBUG('t', "\nSystem call Join\n\n");
            //int Join(int id)
            
            int id = machine->ReadRegister(4);
            machine->WriteRegister(2, -1);
            if(id >= 0 && currentThread->child != NULL) {
                ChildThread* record = currentThread->child;
                do {
                    if(record->id == id) {
                        DEBUG('t',"Waiting on child thread\n");
                        record->join_sem->Wait();
                        machine->WriteRegister(2, record->status);
                        break;
                    }
                    record = record->next;
                } while(record != currentThread->child);
            }

            DEBUG('t',"REGISTER READS: %d\n",machine->ReadRegister(2));
            machine->WriteRegister(PrevPCReg, machine->ReadRegister(PCReg));
            machine->WriteRegister(PCReg, machine->ReadRegister(NextPCReg));
            machine->WriteRegister(NextPCReg, machine->ReadRegister(PCReg)+4);

        } else if (type == SC_Exec) {
            //SpaceId Exec(char *name, char* args[]);
            char* name = currentThread->space->read(
                machine->ReadRegister(4), 0);
            int args = machine->ReadRegister(5);
            Thread* thread = new Thread("user", currentThread);

            DEBUG('t', "\nSystem call Exec\n\n");

            SpaceId rtn = -1;

            DEBUG('t',"\nNAME OF EXECUTABLE: %s\n\n", name);

            try {
                thread->space = new AddrSpace(name);
                if(args != 0) {
                    thread->space->stackArgs(args, name);
                }
                rtn = thread->getID();
                thread->Fork(&machine_run, 0);

                DEBUG('t', "Successful return with id of %d\n", rtn);
            } catch (int e) {
                DEBUG('t', "Failed to create address space for \"%s\".\n",
                      name);
                delete thread;
            }
            DEBUG('t', "Exec returns with value %d\n", rtn);
            machine->WriteRegister(2, (int)rtn);
            machine->WriteRegister(PrevPCReg, machine->ReadRegister(PCReg));
            machine->WriteRegister(PCReg, machine->ReadRegister(NextPCReg));
            machine->WriteRegister(NextPCReg, machine->ReadRegister(PCReg)+4);
            DEBUG('t',"PCREGISTER is %x\n\n", PCReg);
        } else if (type == SC_Fork) {
            //TODO change PC for child thread and set its return value to zero.
            Thread* thread = new Thread("forked", currentThread);

            DEBUG('t', "\nSystem call Fork\n\n");

            thread->space = new AddrSpace(*(currentThread->space));
            thread->Fork(&run_forked, 0);

            machine->WriteRegister(2, thread->getID());
            machine->WriteRegister(PrevPCReg, machine->ReadRegister(PCReg));
            machine->WriteRegister(PCReg, machine->ReadRegister(NextPCReg));
            machine->WriteRegister(NextPCReg, machine->ReadRegister(PCReg)+4);
        } else if (type == SC_Create) {
            //read registers
            int r4 = machine->ReadRegister(4); //address of file name
            char* buffer = currentThread->space->read(r4, 0);

            DEBUG('t', "\nSystem call Create\n\n");
            /*
                int* name = (int*) malloc(100);
                DEBUG('a',"Create reg = %d name = %s\n",r4, "G");
                machine->ReadMem(r4, 4, name);
                DEBUG('a',"Create reg = %d name = %s\n",r4, name);
                machine->ReadMem(r4+4, 4, name+1);
                DEBUG('a',"Create reg = %d name = %s\n",r4, name);
                DEBUG('a',"Create addr = %d\n", name);
                free(name);
            */
            //increment PC
            machine->WriteRegister(PrevPCReg, machine->ReadRegister(PCReg));
            machine->WriteRegister(PCReg, machine->ReadRegister(NextPCReg));
            machine->WriteRegister(NextPCReg, machine->ReadRegister(PCReg)+4);

            //ileSystem->Create(buffer, 0);
            int id = open(buffer, O_CREAT, 0777);
            perror("create:");
            close(id);

        } else if (type == SC_Create) {

            //read registers
            int r4 = machine->ReadRegister(4); //address of file name

            //get the name of the file
            char* name = currentThread->space->read(r4, 0);

            DEBUG('t', "\nSystem call Create\n\n");

            fileSystem->Create(name, 0);
            DEBUG('f', "done creating\n");
            
            //increment PC
            machine->WriteRegister(PrevPCReg, machine->ReadRegister(PCReg));
            machine->WriteRegister(PCReg, machine->ReadRegister(NextPCReg));
            machine->WriteRegister(NextPCReg, machine->ReadRegister(PCReg)+4);
        } else if (type == SC_Open) {
            //read registers
            int r4 = machine->ReadRegister(4); //address of file name
            
            //get the name of the file
            char* name = currentThread->space->read(r4, 0);
            
            DEBUG('t', "\nSystem call Open\n\n");

            OpenFile* file = fileSystem->Open(name);
            int id = currentThread->addFileHandle(name, file);
            machine->WriteRegister(2, id);

            //increment PC
            machine->WriteRegister(PrevPCReg, machine->ReadRegister(PCReg));
            machine->WriteRegister(PCReg, machine->ReadRegister(NextPCReg));
            machine->WriteRegister(NextPCReg, machine->ReadRegister(PCReg)+4);
        } else if (type == SC_Read) {
            //read registers
            int r4 = machine->ReadRegister(4); // buffer address
            int r5 = machine->ReadRegister(5); // number of bytes to read
            int r6 = machine->ReadRegister(6); // file id
            char buffer[r5];

            DEBUG('t', "\nSystem call Read\n\n");
            DEBUG('f',"Reading %d bytes, from fd %d into buffer %d\n",
                r5, r6, r4);

            if(r6 == ConsoleInput){
               int ret = read(ConsoleInput, buffer, r5);
               machine->WriteRegister(2, ret);

            } else if( r6 != ConsoleOutput){

                //look up opened files to find object
                OpenFile* file = currentThread->getFileHandle(r6);
                int ret = file->Read(buffer, r5);
                //machine->WriteRegister(2,read(r6, buffer, r5));
                machine->WriteRegister(2, ret);
            }

            currentThread->space->write(r4, buffer, r5);

            //increment PC
            machine->WriteRegister(PrevPCReg, machine->ReadRegister(PCReg));
            machine->WriteRegister(PCReg, machine->ReadRegister(NextPCReg));
            machine->WriteRegister(NextPCReg, machine->ReadRegister(PCReg)+4);

        } else if (type == SC_Write) {
            //read registers
            int r4 = machine->ReadRegister(4); // buffer address
            int r5 = machine->ReadRegister(5); // number of bytes to read
            int r6 = machine->ReadRegister(6); // file id
            
            char* buffer = currentThread->space->read(r4, r5);

            DEBUG('t', "\nSystem call Write\n\n");

            if(r6 == ConsoleOutput) {
                //unix specific command to write to stdout.
                DEBUG('f',"About to print: %s\n",buffer);
                write(ConsoleOutput, buffer, r5);

            } else if (r6 != ConsoleInput){
                //look up opened files to find object
                OpenFile* file = currentThread->getFileHandle(r6);

                //copy write buffer
                file->Write(buffer, r5);
            }

            //increment PC
            machine->WriteRegister(PrevPCReg, machine->ReadRegister(PCReg));
            machine->WriteRegister(PCReg, machine->ReadRegister(NextPCReg));
            machine->WriteRegister(NextPCReg, machine->ReadRegister(PCReg)+4);
        } else if (type == SC_Close) {
            //read registers
            int r4 = machine->ReadRegister(4); // file id

            DEBUG('t', "\nSystem call Close\n\n");
            
            //look up opened files to find object
            currentThread->closeFileHandle(r4);

            //increment PC
            machine->WriteRegister(PrevPCReg, machine->ReadRegister(PCReg));
            machine->WriteRegister(PCReg, machine->ReadRegister(NextPCReg));
            machine->WriteRegister(NextPCReg, machine->ReadRegister(PCReg)+4);
        } else if (type == SC_Yield) {
            DEBUG('t', "\nSystem call Yield\n\n");
            currentThread->Yield();
        } else if (type == SC_Kill) {

            int signal = machine->ReadRegister(4);
            int proc_id = machine->ReadRegister(5);
            ChildThread *start = currentThread->child;

            DEBUG('t', "\nSystem call Kill\n\n");

            int return_val = -1;

            if (start != NULL) {
                DEBUG('t',"KILL: Got children processes, address is %p\n",
                    start);

                ChildThread *curChild = start;
                int found = 0; 
                do {
                    if (curChild->id == proc_id) {
                        DEBUG('t', "KILL: Found the child process\n");
                        found = 1;
                        break;
                    }
                    curChild = curChild->next;

                } while (curChild != start);


                if (found == 1) {
                    DEBUG(
                        't',
                        "KILL: KILL WAS CALLED WITH ARG %d to proc_id %d\n",
                        signal, proc_id
                    );
                    //do the interrupt fun here
                    return_val = sendKillSig(signal, curChild->thread);
                }
            } 
        
            machine->WriteRegister(PrevPCReg, machine->ReadRegister(PCReg)    );
            machine->WriteRegister(PCReg,     machine->ReadRegister(NextPCReg));
            machine->WriteRegister(NextPCReg, machine->ReadRegister(PCReg) + 4);

        } else if (type == SC_SHMGET) {
            DEBUG('s',"CALLED Shmget\n");
            int key = machine->ReadRegister(4);
            int numbytes = machine->ReadRegister(5);
            int flag = machine->ReadRegister(6);

            int ret = allocateSharedMemory(key,numbytes,flag); 
            machine->WriteRegister(2, ret);
            DEBUG('s',"CALLED Shmge endt\n");

            //increment PC
            machine->WriteRegister(PrevPCReg, machine->ReadRegister(PCReg));
            machine->WriteRegister(PCReg, machine->ReadRegister(NextPCReg));
            machine->WriteRegister(NextPCReg, machine->ReadRegister(PCReg)+4);


        } else if (type == SC_SHMAT) {
            DEBUG('s',"CALLED Shmat\n");
            int key = machine->ReadRegister(4);
            void* addr = attachSharedMemory(key);

            machine->WriteRegister(2, (int)addr);
            //increment PC
            machine->WriteRegister(PrevPCReg, machine->ReadRegister(PCReg));
            machine->WriteRegister(PCReg, machine->ReadRegister(NextPCReg));
            machine->WriteRegister(NextPCReg, machine->ReadRegister(PCReg)+4);


        } else {
            DEBUG('a',"Unexpected user mode exception %d %d\n", which, type);

            //ASSERT(FALSE);
            machine->WriteRegister(PrevPCReg, machine->ReadRegister(PCReg)    );
            machine->WriteRegister(PCReg,     machine->ReadRegister(NextPCReg));
            machine->WriteRegister(NextPCReg, machine->ReadRegister(PCReg) + 4);

            machine->WriteRegister(2, -1);

        }
    //Address referenced outside legal bounds.
    } else if(which == AddressErrorException) {
        IntStatus tmp = interrupt->SetLevel(IntOff);
        printf("Segfault\n");
        currentThread->Finish();
        interrupt->SetLevel(tmp);

    //Page is invalid.
    //Implies that page hasn't been generated yet, or is on disk.
    } else if(which == PageFaultException) {
        int bad_addr = machine->registers[BadVAddrReg];
        DEBUG('t', "PageFault on reference to: 0x%x, on Page: %d\n", bad_addr,
            bad_addr / PageSize);

        if(currentThread->space == NULL) {
            DEBUG('t', "Pagefault on thread with no address space.\n");
            currentThread->notifyParent(-1);
            currentThread->Finish();
        }

        if(currentThread->space->load_page(bad_addr / PageSize) == -1) {
            currentThread->notifyParent(-1);
            currentThread->Finish();
        }
    //Write called on read only page.
    } else if(which == ReadOnlyException) {
        int bad_addr = machine->registers[BadVAddrReg];

        DEBUG('t', "Read only exception on address:%d\n", bad_addr);

        if(currentThread->space->allow_writes(bad_addr / PageSize) == -1) {
            currentThread->notifyParent(-1);
            currentThread->Finish();
        }
    }
}
